/**
 * Software License, Version 1.0
 * 
 * Copyright 2008 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgment:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgment may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or Granules, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called Granules, 
 * nor may Indiana University or Community Grids Lab or Granules appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 * NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 * GENERATED USING SOFTWARE.
 */
package cgl.granules.operation;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Properties;

import cgl.granules.dataset.DatasetCollection;
import cgl.granules.dataset.DatasetException;
import cgl.granules.dataset.DatasetFactory;
import cgl.granules.util.MarshallingUtil;
import cgl.narada.util.UUIDRetriever;

/**
 * This is an implementation of the <code>Operation</code> and
 * <code>OperationConfiguration</code> interfaces. This is an abstract class,
 * and the only method that classes extending this class need to implement is
 * the <code>execute()</code> method.
 * 
 * @author Shrideep Pallickara
 * 
 */
public abstract class OperationBase implements Operation,
		OperationConfiguration {
	private String operationIdentifier;
	private String instanceIdentifier;

	private boolean needsInitialization = false;
	private boolean isInitialized = false;

	private boolean terminationConditionReached = false;
	private boolean hasProcessingDirectives = false;
	private Properties processingDirectives = null;

	private boolean hasExecutionProfile = false;

	private String domain;
	private long versionInformation;

	private DatasetCollection datasetCollection = null;
	private boolean hasDatasetCollection = false;
	private String binding = null;
	private ExecutionProfileSetup executionProfile = null;
	private DatasetFactory datasetFactory;

	public OperationBase() {
		UUIDRetriever retriever = UUIDRetriever.getInstance();
		binding = retriever.getRandomBasedUUIDAsString();
		instanceIdentifier = retriever.getRandomBasedUUIDAsString();
		executionProfile = new ExecutionProfileSetup(binding);
		hasExecutionProfile = true;
		datasetFactory = DatasetFactory.getInstance();
	}

	@Override
	public void unmarshall(byte[] marshalledBytes) throws IOException,
			DatasetException {
		ByteArrayInputStream baInputStream = new ByteArrayInputStream(
				marshalledBytes);
		DataInputStream din = new DataInputStream(baInputStream);
		MarshallingUtil marshaller = MarshallingUtil.getInstance();

		String className = marshaller.readString(din);
		domain = marshaller.readString(din);
		operationIdentifier = marshaller.readString(din);
		instanceIdentifier = marshaller.readString(din);
		versionInformation = din.readLong();

		needsInitialization = din.readBoolean();
		hasProcessingDirectives = din.readBoolean();
		if (hasProcessingDirectives) {
			processingDirectives = marshaller.readProperties(din);
		}

		hasDatasetCollection = din.readBoolean();
		if (hasDatasetCollection) {
			int datasetCollectionLength = din.readInt();
			byte[] datasetCollectionBytes = new byte[datasetCollectionLength];
			din.readFully(datasetCollectionBytes);
			datasetCollection = datasetFactory
					.getDatasetCollection(datasetCollectionBytes);
			datasetCollection.initializeDatasets();
		}

		hasExecutionProfile = din.readBoolean();
		if (hasExecutionProfile) {
			byte[] executionProfileBytes = new byte[din.readInt()];
			din.readFully(executionProfileBytes);
			executionProfile = new ExecutionProfileSetup(executionProfileBytes);
		}

	}

	@Override
	public byte[] getBytes() throws IOException {
		MarshallingUtil marshaller = MarshallingUtil.getInstance();
		byte[] marshalledBytes = null;
		ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream();
		DataOutputStream dout = new DataOutputStream(baOutputStream);

		String className = getClass().getCanonicalName();
		marshaller.writeString(dout, className);
		marshaller.writeString(dout, domain);
		marshaller.writeString(dout, operationIdentifier);
		marshaller.writeString(dout, instanceIdentifier);
		dout.writeLong(versionInformation);

		dout.writeBoolean(needsInitialization);
		dout.writeBoolean(hasProcessingDirectives);
		if (hasProcessingDirectives) {
			marshaller.writeProperties(dout, processingDirectives);
		}

		dout.writeBoolean(hasDatasetCollection);
		if (hasDatasetCollection) {
			byte[] datasetCollectionBytes = datasetCollection.getBytes();
			dout.writeInt(datasetCollectionBytes.length);
			dout.write(datasetCollectionBytes);
		}

		dout.writeBoolean(hasExecutionProfile);
		if (hasExecutionProfile) {
			byte[] executionProfileBytes = executionProfile.getBytes();
			dout.writeInt(executionProfileBytes.length);
			dout.write(executionProfileBytes);
		}

		dout.flush();
		marshalledBytes = baOutputStream.toByteArray();
		return marshalledBytes;
	}

	@Override
	public void dispose() {
		// TODO Auto-generated method stub

	}

	@Override
	public String getDomain() {
		return domain;
	}

	@Override
	public ExecutionProfile getExecutionProfile() {
		return executionProfile;
	}

	@Override
	public String getOperationIdentifier() {
		return operationIdentifier;
	}

	public void setOperationIdentifier(String operationIdentifier) {
		this.operationIdentifier = operationIdentifier;
	}

	/**
	 * @param instanceIdentifier
	 *            the instanceIdentifier to set
	 */
	public void setInstanceIdentifier(String instanceIdentifier) {
		this.instanceIdentifier = instanceIdentifier;
	}

	/**
	 * @return the instanceIdentifier
	 */
	public String getInstanceIdentifier() {
		return instanceIdentifier;
	}

	@Override
	public Properties getProcessingDirectives() {
		return processingDirectives;
	}

	public void setProcessingDirectives(Properties processingDirectives) {
		this.processingDirectives = processingDirectives;
		if (processingDirectives != null) {
			hasProcessingDirectives = true;
		} else {
			hasProcessingDirectives = false;
		}
	}

	@Override
	public long getVersionInformation() {
		return versionInformation;
	}

	public abstract void execute() throws ProcessingException;

	@Override
	public boolean hasExecutionProfile() {
		return hasExecutionProfile;
	}

	@Override
	public boolean hasProcessingDirectives() {
		return hasProcessingDirectives;
	}

	@Override
	public void initialize(Properties processingDirectives)
			throws ProcessingException {
		/* This method should be overridden by the operation in question. */

	}

	@Override
	public boolean terminationConditionReached() {
		return terminationConditionReached;
	}

	@Override
	public boolean isInitialized() {
		return isInitialized;
	}

	public void markInitializationAsComplete() {
		isInitialized = true;
	}

	@Override
	public boolean needsInitialization() {
		return needsInitialization;
	}

	@Override
	public String toString() {
		String stringRepresentation = "Operation: " + operationIdentifier
				+ " Domain=" + domain + ", Version=" + versionInformation
				+ ", hasDataset=" + hasDatasetCollection();

		if (hasDatasetCollection()) {
			stringRepresentation += datasetCollection.toString();
		}

		stringRepresentation += (", needsInitialization=" + needsInitialization);
		if (needsInitialization) {
			stringRepresentation += (", isInitialized=" + isInitialized);
		}

		stringRepresentation += (", ExecutionProfile= " + executionProfile);
		return stringRepresentation;
	}

	@Override
	public DatasetCollection getDatasetCollection() {
		return datasetCollection;
	}

	@Override
	public boolean hasDatasetCollection() {
		return hasDatasetCollection;
	}

	@Override
	public void setDatasetCollection(DatasetCollection datasetCollection) {
		this.datasetCollection = datasetCollection;
		if (datasetCollection != null) {
			hasDatasetCollection = true;
		} else {
			hasDatasetCollection = false;
		}
	}

	@Override
	public void setDomain(String domain) {
		this.domain = domain;
	}

	@Override
	public void setInitializationComplete() {
		isInitialized = true;
	}

	@Override
	public void setInitializationNeeded(boolean needsInitialization) {
		this.needsInitialization = needsInitialization;
	}

	@Override
	public void setTeminationConditionReached() {
		terminationConditionReached = true;
	}

	@Override
	public void setVersionInformation(long versionInformation) {
		this.versionInformation = versionInformation;
	}

	@Override
	public void setAsExecuteFixedNumberOfTimes(int numOfTimes) {
		executionProfile.setAsExecuteFixedNumberOfTimes(binding, numOfTimes);
	}

	@Override
	public void setAsExecuteOnce() {
		executionProfile.setAsExecuteOnce(binding);
	}

	@Override
	public void setAsExecutePeriodically(long executionInterval) {
		executionProfile.setAsExecutePeriodically(binding, executionInterval);
	}

	@Override
	public void setAsStayAlive() {
		executionProfile.setAsStayAlive(binding);
	}

	@Override
	public void setAsExecuteWhenDataAvailable() {
		executionProfile.setAsExecuteWhenDataAvailable(binding);
	}

	/**
	 * This is a protected method, which allows only the sub-classes of
	 * Operation to change the execution profile of the operation appropriately.
	 * 
	 * @param numOfTimes
	 *            The number of times the operation should be executed.
	 */
	protected void changeToExecuteFixedNumberOfTimes(int numOfTimes) {
		executionProfile.changeToExecuteFixedNumberOfTimes(numOfTimes);
	}

	/**
	 * This is a protected method, which allows only the sub-classes of
	 * Operation to change the execution profile of the operation appropriately.
	 * 
	 */
	protected void changeToExecuteOnce() {
		executionProfile.changeToExecuteOnce();
	}

	/**
	 * This is a protected method, which allows only the sub-classes of
	 * Operation to change the execution profile of the operation appropriately.
	 * 
	 * @param executionInterval
	 *            The intervals at which the operation should be executed.
	 */
	protected void changeToExecutePeriodically(long executionInterval) {
		executionProfile.changeToExecutePeriodically(executionInterval);
	}

	/**
	 * This is a protected method, which allows only the sub-classes of
	 * Operation to change the execution profile of the operation appropriately.
	 */
	protected void changeToStayAlive() {
		executionProfile.changeToStayAlive();
	}

	/**
	 * This is a protected method, which allows only the sub-classes of
	 * Operation to change the execution profile of the operation appropriately.
	 */
	protected void changeToExecuteWhenDataAvailable() {
		executionProfile.changeToExecuteWhenDataAvailable();
	}

}
